package com.ruoyi.framework.web.service;

import javax.annotation.Resource;

import com.ruoyi.common.constant.RedisConstants;
import com.ruoyi.common.constant.SysConstants;
import com.ruoyi.common.core.domain.model.LoginBody;
import com.ruoyi.common.utils.*;
import com.ruoyi.common.utils.sign.Md5Utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.CaptchaExpireException;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysUserService;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 登录校验方法
 *
 * @author ruoyi
 */
@Component
public class SysLoginService {
    @Autowired
    private TokenService tokenService;

    @Resource
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private ISysUserService userService;

    @Autowired
    private ISysConfigService configService;

    /**
     * 登录验证
     *
     * @param username 用户名
     * @param password 密码
     * @param code     验证码
     * @param uuid     唯一标识
     * @return 结果
     */
    public String login(String username, String password, String code, String uuid) {
        boolean captchaOnOff = configService.selectCaptchaOnOff();
        // 验证码开关
        if (captchaOnOff) {
            validateCaptcha(username, code, uuid);
        }
        // 用户验证
        Authentication authentication = null;
        //用户登录IP
        String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
        String failureKey = "login_failure:" + Md5Utils.hash(ip + username);

        try {
            Integer num = redisCache.getCacheObject(failureKey);
            if (StringUtils.isNotNull(num) && num > SysConstants.CONSTANT_INT5) {
                throw new ServiceException("，请10分钟后重试");
            } else {
                // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
                authentication = authenticationManager
                        .authenticate(new UsernamePasswordAuthenticationToken(username, password));
            }
            //绑定设备

        } catch (Exception e) {
            String message = limitLogin(failureKey);    //错误次数限制，5次
            if (e instanceof BadCredentialsException) {
                String matchMessage = MessageUtils.message("user.password.not.match") + message;
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, matchMessage));
                throw new ServiceException(matchMessage);
            } else {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                if(e.getCause() instanceof ServiceException){
                    throw new ServiceException(e.getMessage() + message ,((ServiceException) e.getCause()).getCode());
                }
                throw new ServiceException(e.getMessage() + message);
            }
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        recordLoginInfo(loginUser.getUserId());
        redisCache.deleteObject(failureKey);
        // 生成token
        return tokenService.createToken(loginUser);
    }

    /**
     * 校验验证码
     *
     * @param username 用户名
     * @param code     验证码
     * @param uuid     唯一标识
     * @return 结果
     */
    public void validateCaptcha(String username, String code, String uuid) {
        String verifyKey = Constants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
        String captcha = redisCache.getCacheObject(verifyKey);
        redisCache.deleteObject(verifyKey);
        if (captcha == null) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
            throw new CaptchaExpireException();
        }
        if (!code.equalsIgnoreCase(captcha)) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
            throw new CaptchaException();
        }
    }

    /**
     * 记录登录信息
     *
     * @param userId 用户ID
     */
    public void recordLoginInfo(Long userId) {
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
        sysUser.setLoginDate(DateUtils.getNowDate());
        userService.updateUserProfile(sysUser);
    }

    /**
     * 同一账号在同一IP下限制10分钟内只允许错误登录5次
     *
     * @param failureKey
     * @return
     */
    public String limitLogin(String failureKey) {
        Integer num = redisCache.getCacheObject(failureKey);
        if (StringUtils.isNull(num)) {
            num = 1;
        } else {
            num += 1;
        }

        if (num > 5) {
            return ",已经输错超过5次";
        } else {
            redisCache.setCacheObject(failureKey, num, RedisConstants.EXPIRE_TIME_10, TimeUnit.MINUTES);
            return ",已经输错" + num + "次";
        }
    }

    /**
     * 检查用户在线状态
     *
     * @param loginBody
     * @return
     */
    public int checkOnline(LoginBody loginBody) {
        String userId = SecurityUtils.getUserId().toString();
        if ((int) loginBody.getOnlineCode() == SysConstants.CONSTANT_INT1001) {
            Long timeVale = 0L;
            if (StringUtils.isNotNull(redisCache.getCacheMapValue(RedisConstants.CHECK_ONLINE, userId))) {
                timeVale = Long.valueOf(redisCache.getCacheMapValue(RedisConstants.CHECK_ONLINE, userId).toString());
            }
            Long timestamp = DateUtils.getNowDate().getTime() / 1000;
            //首次登录
            if (timeVale == 0) {
                redisCache.setCacheMapValue(RedisConstants.ONLINE_FIRST, userId, timestamp);
            }
            //工作模式
            if (StringUtils.isNotNull(loginBody.getPattern())) {
                redisCache.setCacheMapValue(RedisConstants.WORK_PATTERN, userId, loginBody.getPattern());
            }
            redisCache.setCacheMapValue(RedisConstants.CHECK_ONLINE, userId, timestamp);
            return SysConstants.CONSTANT_INT1002;
        } else {
            throw new ServiceException("在线状态编码不正确！");
        }
    }

    /**
     * 检查用户在线状态，设置60s过期
     *
     * @return
     * @para
     */
    public Integer updateOnlineStatus() {
        //首次登录
        Map<String, String> onlineFirst = redisCache.hGetAll(RedisConstants.ONLINE_FIRST);
        Long[] ids = new Long[onlineFirst.size()];
        int j = 0;
        for (String key : onlineFirst.keySet()) {
            ids[j] = Long.valueOf(key);
            //删除key
            redisCache.delCacheMapValue(RedisConstants.ONLINE_FIRST, key);
            j++;
        }
        if (ids.length > 0) {
            SysUser sysUser = new SysUser();
            sysUser.setUserIds(ids);
            sysUser.setOnlineStatus(SysConstants.CONSTANT_STR0);
            userService.updateOnlineStatus(sysUser);
        }
        //离线
        Map<String, String> checkOnline = redisCache.hGetAll(RedisConstants.CHECK_ONLINE);
        Long[] userIds = new Long[checkOnline.size()];
        int i = 0;
        for (String key : checkOnline.keySet()) {
            //时间戳差
            Long diffTime = DateUtils.getNowDate().getTime() / 1000 - Long.valueOf(String.valueOf(checkOnline.get(key)));
            if (diffTime > SysConstants.CHECK_ONLINE_DIFF) {
                userIds[i] = Long.valueOf(key);
                i++;
                //删除key
                redisCache.delCacheMapValue(RedisConstants.CHECK_ONLINE, key);
                //删除工作模式
                redisCache.delCacheMapValue(RedisConstants.WORK_PATTERN, key);
            }
        }
        if (StringUtils.isNotNull(userIds[0])) {
            SysUser sysUser = new SysUser();
            sysUser.setUserIds(userIds);
            sysUser.setOnlineStatus(SysConstants.CONSTANT_STR1);
            userService.updateOnlineStatus(sysUser);
        }

        return SysConstants.CONSTANT_INT1002;
    }
}
